#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>

#define BUFSIZE 1024
#define PKGLEN 500

typedef int SOCK;
typedef struct sockaddr_in SOCKADDR;

void request(SOCK conn);

int main(int argc, char *argv[]) {

    SOCK client;
    SOCKADDR servAddr;

    if(argc != 2) {
        printf("请输入服务器IP地址...\n");
        exit(0);
    }

    if((client = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        printf("create sock error(%d): %s\n", errno, strerror(errno));
        exit(0);
    }

    memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_port = htons(7288);
    if(inet_pton(AF_INET, argv[1], &servAddr.sin_addr) == -1) {
        printf("net address error(%d): %s\n", errno, strerror(errno));
        exit(0);
    }

    if((connect(client, (struct sockaddr*)&servAddr, sizeof(servAddr))) == -1) {
        printf("connect to server error(%d): %s\n", errno, strerror(errno));
        exit(0);
    }

    request(client);

    close(client);

    return 0;
}


char divider[] = {
    "=========================================\n"
};
char options[] = {
    "欢迎来到文件系统，请选择需要的功能:\n"
    "send:          向服务器传送一个文件\n"
    "download:      从服务器下载一个文件\n"
    "quit:          退出程序\n"
};
char inputTip[] = {
    "请输入操作指令: "
};

void sendFile(SOCK conn, char *fileName);
void sendFileUploadRequest(SOCK conn, char *fileName, int fileLen);
void sendFileContent(SOCK conn, FILE *file, int fileLen);

void sendFileListRequest(SOCK conn);

int sendDownloadFileRequest(SOCK conn, char *fileName);
void recvFile(SOCK conn, char *fileName, int fileLen);
void downloadFile(SOCK conn, char *fileName);

void sendCloseRequest(SOCK conn);

void request(SOCK conn) {

    char option[32];
    char fileName[32];
    while(1) {
        printf("%s%s%s", divider, options, inputTip);
        scanf("%s", option);
        switch (option[0])
        {
        case 's':
            printf("请输入要传送的文件名:");
            scanf("%s", fileName);
            sendFile(conn, fileName);
            break;
        case 'd':
            printf("服务器端的文件列表:\n");
            sendFileListRequest(conn);
            printf("请输入需要下载的文件名:");
            scanf("%s", fileName);
            downloadFile(conn, fileName);
            break;
        case 'q':
            sendCloseRequest(conn);
            return ;
        default:
            break;
        }
    }

}

void sendCloseRequest(SOCK conn) {
    char sendBuf[BUFSIZE] = {0};
    sendBuf[0] = '0';
    int sentLen;
    if((sentLen = send(conn, sendBuf, 1, 0)) == -1) {
        printf("send error(%d): %s\n", errno, strerror(errno));
        exit(0);
    }
}


int sendDownloadFileRequest(SOCK conn, char *fileName) {
    char sendBuf[BUFSIZE] = {0};
    sendBuf[0] = '4';
    strncpy(sendBuf + 1, fileName, strlen(fileName) + 1);
    if(send(conn, sendBuf, 33, 0) == -1) {
        printf("send error(%d): %s\n", errno, strerror(errno));
        exit(0);
    }
    char recvBuf[BUFSIZ] = {0};
    if(recv(conn, recvBuf, sizeof(recvBuf), 0) == -1) {
        printf("recv error(%d): %s\n", errno, strerror(errno));
        exit(0);
    }
    if(recvBuf[0] != '4') {
        printf("unknown error\n");
        exit(0);
    }
    return *(int *)(recvBuf + 1);
}
void recvFile(SOCK conn, char *fileName, int fileLen) {
    FILE *file;
    if((file = fopen(fileName, "w")) == NULL) {
        printf("create file %s error(%d): %s\n", errno, strerror(errno));
        exit(0);
    }
    char sendBuf[BUFSIZE] = {0};
    sendBuf[0] = '9';
    if(send(conn, sendBuf, 1, 0) == -1) {
        printf("send error(%d): %s\n", errno, strerror(errno));
        exit(0);
    }
    char recvBuf[BUFSIZE] = {0}, ackBuf[] = "7";
    printf("接收中...\n");
    int recvLen = 0, bytesRecv = 0;
    while(bytesRecv < fileLen) {
        if((recvLen = recv(conn, recvBuf, sizeof(recvBuf), 0)) <= 0) {
            printf("recv error(%d): %s\n", errno, strerror(errno));
            exit(0);
        }
        // printf("%s %d %s %d\n", recvBuf, *(int*)(recvBuf + 1), recvBuf + 5, recvLen);

        if(recvBuf[0] != '5') {
            printf("unknown error: %s\n", recvBuf[0]);
            exit(0);
        }
        if(send(conn, ackBuf, 1, 0) == -1) {
            printf("send error(%d): %s\n", errno, strerror(errno));
            exit(0);
        }
        recvLen = *(int *)(recvBuf + 1);
        fwrite(recvBuf + 5, sizeof(char), recvLen, file);
        bytesRecv += recvLen;
        // printf("%d %d\n", bytesRecv, fileLen);
    }
    fflush(file);
    fclose(file);
    printf("接收完成!\n");
}
void downloadFile(SOCK conn, char *fileName) {
    int fileLen = sendDownloadFileRequest(conn, fileName);
    if(!fileLen) {
        printf("file not exist!\n");
        return ;
    }
    recvFile(conn, fileName, fileLen);
}

// upper bound of number of file: 10
void sendFileListRequest(SOCK conn) {
    char sendBuf[BUFSIZ] = {0};
    sendBuf[0] = '2';
    if(send(conn, sendBuf, 1, 0) == -1) {
        printf("send error(%d): %s\n", errno, strerror(errno));
        exit(0);
    }
    char recvBuf[BUFSIZE] = {0};
    int recvLen;
    if((recvLen = recv(conn, recvBuf, sizeof(recvBuf), 0)) == -1) {
        printf("recv error(%d): %s\n", errno, strerror(errno));
        exit(0);
    }
    if(recvBuf[0] != '3') {
        printf("unknown error\n");
        exit(0);
    }
    int fileCount = *(int *)(recvBuf + 1);
    int i;
    printf("%d\n", fileCount);
    for(i = 0; i < fileCount; i++) {
        printf("%d. %s\n", i + 1, recvBuf + 5 + i * 32);
    }
}

void sendFile(SOCK conn, char *fileName) {
    FILE *file;
    if((file = fopen(fileName, "r")) == NULL) {
        printf("open file: %s error(%d): %s\n", fileName, errno, strerror(errno));
        return ;
    }
    int fileLen;
    fseek(file, 0, SEEK_END);
    fileLen = ftell(file) - 1;
    fseek(file, 0, SEEK_SET);
    printf("传输中...\n");

    sendFileUploadRequest(conn, fileName, fileLen);

    char recvBuf[BUFSIZE] = {0};
    recv(conn, recvBuf, sizeof(recvBuf), 0);

    if(recvBuf[0] == '1') {
        // printf("server ready to recv\n");
        sendFileContent(conn, file, fileLen);
    }

    fclose(file);
    printf("传输完成!\n");

}
void sendFileUploadRequest(SOCK conn, char *fileName, int fileLen) {
    char sendBuf[BUFSIZE] = {0};
    int fileNameLen = strlen(fileName);
    sendBuf[0] = '1';
    strncpy(sendBuf + 1, fileName, fileNameLen);
    *(sendBuf + 1 + fileNameLen) = '\0';
    *(int *)(sendBuf + 33) = fileLen;
    sendBuf[38] = '\0';
    
    int sentLen;
    if((sentLen = send(conn, sendBuf, 39, 0)) == -1) {
        printf("send file upload request error(%d): %s\n", errno, strerror(errno));
        exit(0);
    }
}
void sendFileContent(SOCK conn, FILE *file, int fileLen) {
    char sendBuf[BUFSIZE], ackBuf[8];
    int bytesSent = 0, sendLen, sentLen;
    while(bytesSent < fileLen) {
        memset(sendBuf, 0, sizeof(sendBuf));
        sendBuf[0] = '5';

        if(bytesSent + PKGLEN > fileLen) sendLen = fileLen - bytesSent;
        else sendLen = PKGLEN;

        *(int *)(sendBuf + 1) = sendLen;
        fread(sendBuf + 5, sizeof(char), sendLen, file);

        if((sentLen = send(conn, sendBuf, sendLen + 5, 0)) == -1) {
            printf("send file content error(%d): %d\n", errno, strerror(errno));
            exit(0);
        }
        // printf("%s %d %s\n", sendBuf, *(int*)(sendBuf + 1), sendBuf + 5);
        if(recv(conn, ackBuf, sizeof(ackBuf), 0) == -1) {
            printf("recv errro(%d): %s\n", errno, strerror(errno));
            exit(0);
        }
        bytesSent += sendLen;
    }
}